package com.jasonchoi.security.commons.config;

import com.jasonchoi.security.commons.security.LoginFailureHandler;
import com.jasonchoi.security.commons.security.LoginSuccessHandler;
import com.jasonchoi.security.commons.security.PermissionDeniedHandler;
import com.jasonchoi.security.commons.security.UrlRoleAuthHandler;
import com.jasonchoi.security.commons.security.UrlRolesFilterHandler;
import com.jasonchoi.security.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.vote.AuthenticatedVoter;
import org.springframework.security.access.vote.RoleVoter;
import org.springframework.security.access.vote.UnanimousBased;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.expression.WebExpressionVoter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

/**
 * 用户认证 通过implement UserDetailsService
 * 动态授权  方式一
 *          自定义implements FilterInvocationSecurityMetadataSource 获取当前资源需要的访问角色
 *                                                                 公共权限调用 返回空集合或者null 不会进入拦截
 *          自定义投票器 implements AccessDecisionVoter<Object>     一个决策器对多个投票器 将角色和资源进行匹配投票
 * @Author: JasonChoi
 * @Date: 2020/1/2 16:29
 */
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UrlRolesFilterHandler urlRolesFilterHandler;

    @Resource
    private UserService userService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        //可自定义passwordEncoder
        //PasswordEncoder passwordEncoder = new MyMD5PasswordEncode();
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 关闭csrf
        http.csrf().disable();

        // 登录配置
        http.formLogin().loginPage("/auth/login").loginProcessingUrl("/login").permitAll()
            .successHandler(new LoginSuccessHandler()).failureHandler(new LoginFailureHandler())
            .and().logout().permitAll();

        // 授权配置
        http.authorizeRequests()
            /* 无需授权访问 */
            .antMatchers("/", "/auth/login").permitAll()
            .antMatchers("/js/**", "/css/**", "/images/**").permitAll()
            .anyRequest().authenticated()
            /* 动态url权限 */
            .withObjectPostProcessor(new DefinedObjectPostProcessor())
            /* url决策 */
            .accessDecisionManager(accessDecisionManager())
        ;

        //权限不足控制器
        http.exceptionHandling().accessDeniedHandler(new PermissionDeniedHandler());
    }

    /**
     * AffirmativeBased – 任何一个AccessDecisionVoter返回同意则允许访问
     * ConsensusBased – 同意投票多于拒绝投票（忽略弃权回答）则允许访问
     * UnanimousBased – 每个投票者选择弃权或同意则允许访问
     *
     * 决策管理
     */
    private AccessDecisionManager accessDecisionManager() {
        List<AccessDecisionVoter<? extends Object>> decisionVoters = new ArrayList<>();
        decisionVoters.add(new WebExpressionVoter());
        decisionVoters.add(new AuthenticatedVoter());
        decisionVoters.add(new RoleVoter());
        /* 路由权限管理 */
        decisionVoters.add(new UrlRoleAuthHandler());
        return new UnanimousBased(decisionVoters);
    }


    class DefinedObjectPostProcessor implements ObjectPostProcessor<FilterSecurityInterceptor> {
        @Override
        public <O extends FilterSecurityInterceptor> O postProcess(O object) {
            object.setSecurityMetadataSource(urlRolesFilterHandler);
            return object;
        }
    }
}
